﻿using System;
using System.Drawing;
using System.Numerics;

namespace Fractals
{
    /// <summary>
    /// http://rosettacode.org/wiki/Mandelbrot_set
    /// </summary>
    class MandelbrotGenerator
    {
        
        //for random color mode
        const int m_maxRandColors = 64;
        Random m_randGen = new Random();
        Color[] m_colorArray = new Color[m_maxRandColors];
        private double m_maxNorm;
        private double m_maxEscapeRadius;

        #region Properties

        public int MaxIterations { get; set; }
        public double MaxEscapeRadius
        {
            set
            {
                m_maxEscapeRadius = value;
                m_maxNorm = value * value;
            }
            get {return m_maxEscapeRadius;}
        }
        public bool SmoothShading { get; set; }
        public int ColorMode { get; set; }

        #endregion

        public MandelbrotGenerator()
        {
            MaxIterations = 20;
            MaxEscapeRadius = 2.0;
            SmoothShading = true;
            ColorMode = 2;
            generateRandomColorArray();
        }

        private void generateRandomColorArray()
        {
            //create random colors
            for (int i = 0; i < m_maxRandColors; i++)
            {
                var v1 = m_randGen.Next();
                var v2 = m_randGen.Next();
                var v3 = m_randGen.Next();
                m_colorArray[i] = Color.FromArgb(v1 % (255), v2 % (255), v3 % (255));
            }
        }

        public void GenerateMandelbrotImage(Bitmap bitmap)
        {
            double scale = 2 * m_maxEscapeRadius / Math.Min(bitmap.Width, bitmap.Height);
            
            for (int y = 0; y < bitmap.Height; y++)
            {
                double imaginary = (y - bitmap.Height / 2) * scale;

                for (int x = 0; x < bitmap.Width; x++)
                {
                    double real = (x - bitmap.Width / 2) * scale;
                    double pixelValue = CalcMandelbrotValue(new Complex(real, imaginary));
                    bitmap.SetPixel(x, y, GetColor(pixelValue));
                }
            }
        }

        private double CalcMandelbrotValue(Complex c)
        {
            // from http://en.wikipedia.org/w/index.php?title=Mandelbrot_set

            int iteration = 0;
            Complex z = new Complex();
            
            do
            {
                z = z * z + c;
                iteration++;
            } while ((z.Real * z.Real + z.Imaginary * z.Imaginary) < m_maxNorm && iteration < MaxIterations);

            if (iteration < MaxIterations)
            {
                if (SmoothShading)
                {
                // "Banding can be eliminated by using a fractional escape count.
                // There are many ways that a fractional escape count can be computed.
                // One possible way of obtaining a fraction is to measure how far the iterated point landed outside of th escape cutoff.
                // One of the smoothest, most mathematically correct formulas is the "renormalized" iteration count."
                // http://linas.org/art-gallery/escape/smooth.html

                    double mu = iteration + 1 - Math.Log( Math.Log( Math.Sqrt(z.Real * z.Real + z.Imaginary * z.Imaginary) ) ) / Math.Log(m_maxEscapeRadius);
                    return mu / MaxIterations;
                }
                else
                    return (double)iteration / MaxIterations;
            }
            else
                return 0;
        }

        private Color GetColor(double value)
        {
            if (value == 0.0)
                return Color.Black;

            var finalColor = new HSLColor(Color.Black);

            //if(SmoothShading == false)
                //const double contrast = 0.20;
                //value = Math.Pow(value, contrast);

            switch (ColorMode)
            {
                case 0:
                    finalColor = Color.FromArgb(0, (int)(value * 255.0), 0);
                    break;
                case 1:
                    finalColor = m_colorArray[(int)(MaxIterations * value)%m_maxRandColors];
                    break;
                case 2:
                    finalColor.Hue = (value * 360.0) % 360.0;
                    finalColor.Saturation = 0.70 * 255.0;
                    finalColor.Luminosity = 0.45 * 255.0;
                    break;
            }

            return finalColor;
        }
    }
}
